| Conditions | 23 |
| Total Lines | 96 |
| Code Lines | 84 |
| Lines | 0 |
| Ratio | 0 % |
| Changes | 0 | ||
Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.
For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.
Commonly applied refactorings include:
If many parameters/temporary variables are present:
Complex classes like indexResourceHook.ts ➔ useResourceIndex often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
| 1 | import { useCallback, useEffect, useMemo, useReducer, useRef } from "react"; |
||
| 43 | |||
| 44 | export function useResourceIndex<T extends { id: number }>( |
||
| 45 | endpoint: string, |
||
| 46 | overrides?: { |
||
| 47 | initialValue?: T[]; // Defaults to an empty list. |
||
| 48 | forceInitialRefresh: boolean; // If you set an initialValue but also want to refresh immediately, set this to true. |
||
| 49 | parseIndexResponse?: (response: Json) => T[]; |
||
| 50 | parseEntityResponse?: (response: Json) => T; |
||
| 51 | resolveEntityEndpoint?: (baseEndpoint: string, entity: T) => string; |
||
| 52 | resolveCreateEndpoint?: (baseEndpoint: string, newEntity: T) => string; |
||
| 53 | handleError?: (error: Error | FetchError) => void; |
||
| 54 | }, |
||
| 55 | ): { |
||
| 56 | values: IndexedObject<T>; |
||
| 57 | indexStatus: ResourceStatus; |
||
| 58 | entityStatus: IndexedObject<ResourceStatus>; |
||
| 59 | create: (newValue: T) => Promise<T>; |
||
| 60 | refresh: () => Promise<T[]>; // Reloads the entire index. |
||
| 61 | update: (newValue: T) => Promise<T>; |
||
| 62 | deleteResource: (id: number) => Promise<void>; |
||
| 63 | } { |
||
| 64 | const initialValue = overrides?.initialValue ?? []; |
||
| 65 | const forceInitialRefresh = |
||
| 66 | overrides?.initialValue !== undefined && overrides?.forceInitialRefresh; |
||
| 67 | const parseIndexResponse = overrides?.parseIndexResponse ?? identity; |
||
| 68 | const parseEntityResponse = overrides?.parseEntityResponse ?? identity; |
||
| 69 | const resolveEntityEndpoint = |
||
| 70 | overrides?.resolveEntityEndpoint ?? |
||
| 71 | ((baseEndpoint, entity): string => `${baseEndpoint}/${entity.id}`); |
||
| 72 | const resolveCreateEndpoint = |
||
| 73 | overrides?.resolveCreateEndpoint ?? |
||
| 74 | ((baseEndpoint, _): string => baseEndpoint); |
||
| 75 | const handleError = |
||
| 76 | overrides?.handleError ?? |
||
| 77 | ((): void => { |
||
| 78 | /* Do nothing. */ |
||
| 79 | }); |
||
| 80 | |||
| 81 | const isSubscribed = useRef(true); |
||
| 82 | |||
| 83 | const [state, dispatch] = useReducer( |
||
| 84 | indexCrudReducer, |
||
| 85 | initialValue, |
||
| 86 | initializeState, |
||
| 87 | ); |
||
| 88 | |||
| 89 | const values = useMemo(() => valuesSelector(state), [state]); |
||
| 90 | const indexStatus = state.indexMeta.status; |
||
| 91 | const entityStatus = useMemo(() => statusSelector(state), [state]); |
||
| 92 | |||
| 93 | const create = useCallback( |
||
| 94 | async (newValue: T): Promise<T> => { |
||
| 95 | dispatch({ |
||
| 96 | type: ActionTypes.createStart, |
||
| 97 | meta: { item: newValue }, |
||
| 98 | }); |
||
| 99 | let json: Json; |
||
| 100 | try { |
||
| 101 | json = await postRequest( |
||
| 102 | resolveCreateEndpoint(endpoint, newValue), |
||
| 103 | newValue, |
||
| 104 | ).then(processJsonResponse); |
||
| 105 | } catch (error) { |
||
| 106 | dispatch({ |
||
| 107 | type: ActionTypes.createReject, |
||
| 108 | payload: error, |
||
| 109 | meta: { item: newValue }, |
||
| 110 | }); |
||
| 111 | } |
||
| 112 | const entity = parseEntityResponse(json) as T; |
||
| 113 | if (isSubscribed.current) { |
||
| 114 | dispatch({ |
||
| 115 | type: ActionTypes.createFulfill, |
||
| 116 | payload: entity, |
||
| 117 | meta: { item: newValue }, |
||
| 118 | }); |
||
| 119 | } |
||
| 120 | return entity; |
||
| 121 | }, |
||
| 122 | [endpoint, resolveCreateEndpoint, parseEntityResponse], |
||
| 123 | ); |
||
| 124 | |||
| 125 | // Unsubscribe from promises when this hook is unmounted. |
||
| 126 | useEffect(() => { |
||
| 127 | return (): void => { |
||
| 128 | isSubscribed.current = false; |
||
| 129 | }; |
||
| 130 | }, []); |
||
| 131 | |||
| 132 | return { |
||
| 133 | values, |
||
| 134 | indexStatus, |
||
| 135 | entityStatus, |
||
| 136 | create, |
||
| 137 | update, |
||
| 138 | refresh, |
||
| 139 | }; |
||
| 143 |